﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SpeakupBackend.Themes;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;

namespace SpeakupTests.SpeakupBackend.Themes
{
    [TestClass]
    public class LevelTests
    {
        [TestMethod]
        public void IsLevelNumberValid()
        {
            //confirm all valid values are recognized as valid
            for (int i = Level.MinValidLevelNumber; i <= Level.MaxValidLevelNumber; i++)
            {
                Assert.IsTrue(Level.IsValidLevelNumber(i));
            }

            //confirm all invalid values are recognized as invalid
            for (int i = Level.MinValidLevelNumber - 10; i < Level.MinValidLevelNumber; i++)
            {
                Assert.IsFalse(Level.IsValidLevelNumber(i));
            }

            for (int i = Level.MaxValidLevelNumber + 10; i > Level.MaxValidLevelNumber; i--)
            {
                Assert.IsFalse(Level.IsValidLevelNumber(i));
            }
        }

        [TestMethod]
        public void IsUnitNumberValid()
        {
            //confirm all valid values are recognized as valid
            for (int i = Level.MinValidUnitNumber; i <= Level.MaxValidUnitNumber; i++)
            {
                Assert.IsTrue(Level.IsValidUnitNumber(i));
            }

            //confirm all invalid values are recognized as invalid
            for (int i = Level.MinValidUnitNumber - 10; i < Level.MinValidUnitNumber; i++)
            {
                Assert.IsFalse(Level.IsValidUnitNumber(i));
            }

            for (int i = Level.MaxValidUnitNumber + 10; i > Level.MaxValidUnitNumber; i--)
            {
                Assert.IsFalse(Level.IsValidUnitNumber(i));
            }
        }

        [TestMethod]
        public void GetLevels()
        {
            IList<Level> list = Level.GetLevels();

            //verify that number of elements is correct
            Assert.AreEqual(list.Count, Level.MaxValidLevelNumber - Level.MinValidLevelNumber + 1);

            for (int i = Level.MinValidLevelNumber; i <= Level.MaxValidLevelNumber; i++)
            {
                int count = (from n in list
                             where n.LevelNumber == i
                             select n).Count();

                //expect exactly 1 found item
                Assert.AreEqual(count, 1);
            }
        }

        [TestMethod]
        public void GetLevelByNumber()
        {
            //verify that valid levels can be obtained
            for (int i = Level.MinValidLevelNumber; i <= Level.MaxValidLevelNumber; i++)
            {
                Level w = Level.GetLevel(i);
                Assert.AreEqual(w.LevelNumber, i);
            }

            //verify that invalid levels can not be obtained
            for (int i = Level.MinValidLevelNumber - 10; i < Level.MinValidLevelNumber; i++)
            {
                bool thrown = false;
                try
                {
                    Level.GetLevel(i);
                }
                catch (ArgumentException)
                {
                    thrown = true;
                }

                Assert.AreEqual(true, thrown);
            }

            for (int i = Level.MaxValidLevelNumber + 10; i > Level.MaxValidLevelNumber; i--)
            {
                bool thrown = false;
                try
                {
                    Level.GetLevel(i);
                }
                catch (ArgumentException)
                {
                    thrown = true;
                }

                Assert.AreEqual(true, thrown);
            }

        }

        [TestMethod]
        public void GetLevelByString()
        {
            //verify that valid levels can be obtained
            for (int i = Level.MinValidLevelNumber; i <= Level.MaxValidLevelNumber; i++)
            {
                Level w = Level.GetLevel(i.ToString());
                Assert.AreEqual(w.LevelNumber, i);
            }

            //verify that invalid levels can not be obtained
            for (int i = Level.MinValidLevelNumber - 10; i < Level.MinValidLevelNumber; i++)
            {
                bool thrown = false;
                try
                {
                    Level.GetLevel(i.ToString());
                }
                catch (ArgumentException)
                {
                    thrown = true;
                }

                Assert.AreEqual(true, thrown);
            }

            for (int i = Level.MaxValidLevelNumber + 10; i > Level.MaxValidLevelNumber; i--)
            {
                bool thrown = false;
                try
                {
                    Level.GetLevel(i.ToString());
                }
                catch (ArgumentException)
                {
                    thrown = true;
                }

                Assert.AreEqual(true, thrown);
            }
        }

        [TestMethod]
        public void GetLevelByInvalidString()
        {
            Random rnd = new Random();

            for (int i = Level.MinValidLevelNumber; i <= Level.MaxValidLevelNumber; i++)
            {
                bool thrown = false;
                try
                {
                    String strLevel = String.Format("{1}{0}", (char)rnd.Next('a', 'z'), i);
                    Level.GetLevel(strLevel);
                }
                catch (FormatException)
                {
                    thrown = true;
                }

                Assert.AreEqual(true, thrown);
            }
        }

        [TestMethod]
        public void GetLevelByUnit()
        {
            int expectedLevel = 1;
            int levelCounter = 0;

            //verify that valid levels can be obtained
            for (int i = Level.MinValidUnitNumber; i <= Level.MaxValidUnitNumber; i++)
            {
                Level w = Level.GetLevelByUnit(i);
                Assert.AreEqual(w.LevelNumber, expectedLevel);

                levelCounter++;
                if (levelCounter >= Level.UnitsPerLevel)
                {
                    expectedLevel++;
                    levelCounter = 0;
                }
            }

            //verify that invalid levels can not be obtained
            for (int i = Level.MinValidUnitNumber - 10; i < Level.MinValidUnitNumber; i++)
            {
                bool thrown = false;
                try
                {
                    Level.GetLevelByUnit(i);
                }
                catch (ArgumentException)
                {
                    thrown = true;
                }

                Assert.AreEqual(true, thrown);
            }

            for (int i = Level.MaxValidUnitNumber + 10; i > Level.MaxValidUnitNumber; i--)
            {
                bool thrown = false;
                try
                {
                    Level.GetLevelByUnit(i);
                }
                catch (ArgumentException)
                {
                    thrown = true;
                }

                Assert.AreEqual(true, thrown);
            }

        }

        [TestMethod]
        public void CreateLevel()
        {
            //create level with valid level number
            for (int i = Level.MinValidLevelNumber; i <= Level.MaxValidLevelNumber; i++)
            {
                PrivateObject privateObject = new PrivateObject(typeof(Level), i);
                Level level = (Level)privateObject.Target;
                Assert.AreEqual(i, level.LevelNumber);
            }

            //verify that invalid levels can not be created
            for (int i = Level.MinValidLevelNumber - 10; i < Level.MinValidLevelNumber; i++)
            {
                bool thrown = false;
                try
                {
                    PrivateObject privateObject = new PrivateObject(typeof(Level), i);
                }
                catch (TargetInvocationException exception)
                {
                    if (exception.InnerException.GetType() == typeof(ArgumentException))
                    {
                        thrown = true;
                    }
                }

                Assert.AreEqual(true, thrown);
            }

            for (int i = Level.MaxValidLevelNumber + 10; i > Level.MaxValidLevelNumber; i--)
            {
                bool thrown = false;
                try
                {
                    PrivateObject privateObject = new PrivateObject(typeof(Level), i);
                }
                catch (TargetInvocationException exception)
                {
                    if (exception.InnerException.GetType() == typeof(ArgumentException))
                    {
                        thrown = true;
                    }
                }

                Assert.AreEqual(true, thrown);
            }
        }

        [TestMethod]
        public void LevelGetLevelGroup()
        {
            foreach (LevelGroup group in LevelGroup.DefaultFactory.GetLevelGroups())
            {
                for (int i = group.MinLevel; i <= group.MaxLevel; i++)
                {
                    Level level = Level.GetLevel(i);

                    Assert.AreEqual(group, level.LevelGroup);
                }
            }
        }

        [TestMethod]
        public void LevelGetUnits()
        {
            int currentUnit = 1;            

            for (int i = Level.MinValidLevelNumber; i <= Level.MaxValidLevelNumber; i++ )
            {
                Level level = Level.GetLevel(i);
                IList<int> units = level.GetUnits();

                Assert.AreEqual(Level.UnitsPerLevel, units.Count);

                for (int u = 0; u < Level.UnitsPerLevel; u++)
                {
                    Assert.IsTrue(units.Contains(currentUnit));
                    currentUnit++;
                }
            }
        }

        [TestMethod]
        public void LevelContainsUnit()
        {
            int currentUnit = 1;
            
            for (int i = Level.MinValidLevelNumber; i <= Level.MaxValidLevelNumber; i++)
            {
                Level level = Level.GetLevel(i);

                for (int u = 0; u < Level.UnitsPerLevel; u++)
                {
                    Assert.IsTrue(level.ContainsUnit(currentUnit));
                    currentUnit++;
                }
            }
        }

        [TestMethod]
        public void LevelGetUnitsAndContainsUnit()
        {
            for (int i = Level.MinValidLevelNumber; i <= Level.MaxValidLevelNumber; i++)
            {
                Level level = Level.GetLevel(i);

                foreach (int unit in level.GetUnits())
                {
                    Assert.IsTrue(level.ContainsUnit(unit));
                }
            }
        }

        [TestMethod]
        public void LevelToString()
        {
            //verify that each level can be deserializedLevel from string.
            for (int i = Level.MinValidLevelNumber; i <= Level.MaxValidLevelNumber; i++)
            {
                Level original = Level.GetLevel(i);
                Level fromString = Level.GetLevel(original.ToString());

                Assert.AreEqual(original.LevelNumber, fromString.LevelNumber);
            }
        }

        [TestMethod]
        public void LevelObjectEquals()
        {
            //verify that all equal object return true for Equals (non-generic)
            for (int i = Level.MinValidLevelNumber; i <= Level.MaxValidLevelNumber; i++)
            {
                PrivateObject privateLevelA = new PrivateObject(typeof(Level), i);
                PrivateObject privateLevelB = new PrivateObject(typeof(Level), i);

                Assert.IsTrue(privateLevelA.Target.Equals(privateLevelB.Target));
            }

            //verify that all non-equal return false for Equals (non-generic)
            for (int i = Level.MinValidLevelNumber; i <= Level.MaxValidLevelNumber; i++)
            {
                for (int j = Level.MinValidLevelNumber; j <= Level.MaxValidLevelNumber; j++)
                {
                    if (i == j) continue;

                    PrivateObject privateLevelA = new PrivateObject(typeof(Level), i);
                    PrivateObject privateLevelB = new PrivateObject(typeof(Level), j);

                    Assert.IsFalse(privateLevelA.Target.Equals(privateLevelB.Target));
                }
            }

            //verify true for same object
            Level sameRefVerifier = Level.GetLevel(Level.MinValidLevelNumber);
            Object sameLevel = sameRefVerifier;
            Assert.IsTrue(sameRefVerifier.Equals(sameLevel));

            //verify that fasle returned for null
            Level nullVerifier = Level.GetLevel(Level.MinValidLevelNumber);
            Object nullObject = null;
            Assert.IsFalse(nullVerifier.Equals(nullObject));

            //verify false is returned for non-level objects
            Level otherObjectVerifier = Level.GetLevel(Level.MinValidLevelNumber);
            Object otherObject = new Object();

            Assert.IsFalse(otherObjectVerifier.Equals(otherObject));
        }

        [TestMethod]
        public void LevelIEquatableEquals()
        {
            //verify that all equal object return true for Equals (non-generic)
            for (int i = Level.MinValidLevelNumber; i <= Level.MaxValidLevelNumber; i++)
            {
                PrivateObject privateLevelA = new PrivateObject(typeof(Level), i);
                PrivateObject privateLevelB = new PrivateObject(typeof(Level), i);

                Level levelA = (Level)privateLevelA.Target;
                Level levelB = (Level)privateLevelB.Target;

                Assert.IsTrue(levelA.Equals(levelB));
            }

            //verify that all non-equal return false for Equals (non-generic)
            for (int i = Level.MinValidLevelNumber; i <= Level.MaxValidLevelNumber; i++)
            {
                for (int j = Level.MinValidLevelNumber; j <= Level.MaxValidLevelNumber; j++)
                {
                    if (i == j) continue;

                    PrivateObject privateLevelA = new PrivateObject(typeof(Level), i);
                    PrivateObject privateLevelB = new PrivateObject(typeof(Level), j);

                    Level levelA = (Level)privateLevelA.Target;
                    Level levelB = (Level)privateLevelB.Target;

                    Assert.IsFalse(levelA.Equals(levelB));
                }
            }

            //verify true for same object
            Level sameRefVerifier = Level.GetLevel(Level.MinValidLevelNumber);
            Assert.IsTrue(sameRefVerifier.Equals(sameRefVerifier));

            //verify that fasle returned for null
            Level nullVerifier = Level.GetLevel(Level.MinValidLevelNumber);
            Level nullLevel = null;
            Assert.IsFalse(nullVerifier.Equals(nullLevel));
        }

        [TestMethod]
        public void LevelGetHashCode()
        {
            Dictionary<int, Level> levelDict = new Dictionary<int, Level>();

            //verify that no two levels have same hash code
            for (int i = Level.MinValidLevelNumber; i <= Level.MaxValidLevelNumber; i++)
            {
                Level level = Level.GetLevel(i);
                Assert.IsFalse(levelDict.Keys.Contains(level.GetHashCode()));

                levelDict[level.GetHashCode()] = level;
            }
        }

        [TestMethod]
        public void LevelObjectCompareTo()
        {
            for (int i = Level.MinValidLevelNumber; i <= Level.MaxValidLevelNumber; i++)
            {
                for (int j = Level.MinValidLevelNumber; j <= Level.MaxValidLevelNumber; j++)
                {
                    Level iLevel = Level.GetLevel(i);
                    Level jLevel = Level.GetLevel(j);

                    int i2j = iLevel.CompareTo((Object)jLevel);
                    int j2i = jLevel.CompareTo((Object)iLevel);

                    if (i < j)
                    {
                        Assert.IsTrue(i2j < 0);
                        Assert.IsTrue(j2i > 0);
                    }
                    else if (i > j)
                    {
                        Assert.IsTrue(i2j > 0);
                        Assert.IsTrue(j2i < 0);
                    }
                    else
                    {
                        Assert.AreEqual(0, i2j);
                        Assert.AreEqual(0, j2i);
                    }

                }
            }

        }

        [TestMethod]
        public void LevelGenericCompareTo()
        {
            for (int i = Level.MinValidLevelNumber; i <= Level.MaxValidLevelNumber; i++)
            {
                for (int j = Level.MinValidLevelNumber; j <= Level.MaxValidLevelNumber; j++)
                {
                    Level iLevel = Level.GetLevel(i);
                    Level jLevel = Level.GetLevel(j);

                    int i2j = iLevel.CompareTo(jLevel);
                    int j2i = jLevel.CompareTo(iLevel);

                    if (i < j)
                    {
                        Assert.IsTrue(i2j < 0);
                        Assert.IsTrue(j2i > 0);
                    }
                    else if (i > j)
                    {
                        Assert.IsTrue(i2j > 0);
                        Assert.IsTrue(j2i < 0);
                    }
                    else
                    {
                        Assert.AreEqual(0, i2j);
                        Assert.AreEqual(0, j2i);
                    }

                }
            }
        }

        [TestMethod]
        public void LevelComaprisonOperators()
        {
            for (int i = Level.MinValidLevelNumber; i <= Level.MaxValidLevelNumber; i++)
            {
                for (int j = Level.MinValidLevelNumber; j <= Level.MaxValidLevelNumber; j++)
                {
                    Level iLevel = Level.GetLevel(i);
                    Level jLevel = Level.GetLevel(j);

                    Assert.AreEqual(i != j, iLevel != jLevel);
                    Assert.AreEqual(i == j, iLevel == jLevel);
                    Assert.AreEqual(i < j, iLevel < jLevel);
                    Assert.AreEqual(i <= j, iLevel <= jLevel);
                    Assert.AreEqual(i > j, iLevel > jLevel);
                    Assert.AreEqual(i >= j, iLevel >= jLevel);

                    Assert.AreEqual(j != i, jLevel != iLevel);
                    Assert.AreEqual(j == i, jLevel == iLevel);
                    Assert.AreEqual(j < i, jLevel < iLevel);
                    Assert.AreEqual(j <= i, jLevel <= iLevel);
                    Assert.AreEqual(j > i, jLevel > iLevel);
                    Assert.AreEqual(j >= i, jLevel >= iLevel);
                }
            }

            Level iNullLevel = null;
            Level jNullLevel = null;
            Level nonNullLevel = Level.GetLevel(Level.MinValidLevelNumber);

            Assert.IsTrue(iNullLevel == jNullLevel);
            Assert.IsTrue(jNullLevel == iNullLevel);

            Assert.IsFalse(iNullLevel != jNullLevel);
            Assert.IsFalse(jNullLevel != iNullLevel);

            Assert.IsFalse(nonNullLevel == iNullLevel);
            Assert.IsFalse(iNullLevel == nonNullLevel);

            Assert.IsTrue(nonNullLevel != iNullLevel);
            Assert.IsTrue(iNullLevel != nonNullLevel);
        }

        [TestMethod]
        public void LevelSerialization()
        {
            BinaryFormatter formatter = new BinaryFormatter();

            for (int i = Level.MinValidLevelNumber; i <= Level.MaxValidLevelNumber; i++)
            {
                using (MemoryStream memStream = new MemoryStream())
                {
                    Level level = Level.GetLevel(i);

                    formatter.Serialize(memStream, level);
                    memStream.Position = 0;
                    Level deserializedLevel = (Level)formatter.Deserialize(memStream);

                    Assert.AreEqual(level.LevelNumber, deserializedLevel.LevelNumber);
                    Assert.IsTrue(Object.ReferenceEquals(level, deserializedLevel));
                }
            }
        }
    }
}
